﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GameEngine.MoveGenerators;
using System.Diagnostics;

namespace GameEngine
{
    /// <summary>
    /// Types of capture constraints
    /// </summary>
    public enum enumCaptureConstraint { None, KingPriority, LongestJumpPriority, CaptureMaximumKings, 
            CaptureNearestKing };
    
    /// <summary>
    /// Class for evaluation capture constraint - CHECK MAN PROMOTION DURING JUMP
    /// </summary>
    public class CaptureConstraintEvaluator
    {
        /// <summary>
        /// GameBoard set by GameRuleControllerBuilder
        /// </summary>
        public GameBoard GameBoard
        { get; set; }

        /// <summary>
        /// Capture constraint list - a sort of priority queue
        /// </summary>
        public List<enumCaptureConstraint> CaptureConstraintList
        { get; set; }

        /// <summary>
        /// Man move generator
        /// </summary>
        public IManMoveGenerator ManMoveGenerator
        { get; set; }

        /// <summary>
        /// King move generator
        /// </summary>
        public IKingMoveGenerator KingMoveGenerator
        { get; set; }

        /// <summary>
        /// Boolean value if white pieces move up gameboard. Set by GameRuleControllerBuilder
        /// </summary>
        public bool WhiteMovesUp
        { get; set; }

        /// <summary>
        /// Continue jump after promotion - e.g. for Russian checkers
        /// </summary>
        public bool ContinueJumpAfterPromotion
        { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public CaptureConstraintEvaluator()
        {
            CaptureConstraintList = new List<enumCaptureConstraint>();
        }

        /// <summary>
        /// Filters out moves according to the king priority
        /// </summary>
        /// <param name="moves">Generated list of moves</param>
        /// <returns></returns>
        protected List<Move> FilterOutKingPriority(List<Move> moves)
        {
            List<Move> priority = new List<Move>();

            foreach (Move move in moves)
            {
                enumFigureType figureType = GameBoard.GetField(move.startPos);
                if (figureType != enumFigureType.Nothing)
                {
                    if (figureType.IsKing() == true)
                        priority.Add(move);
                }
                
            }

            if (priority.Count > 0)
                return priority;
            else
                return moves;
        }

        /// <summary>
        /// Counts move length - number of steps
        /// </summary>
        private int CountMoveLength(Move move)
        {
            Move m = move;
            int count = 0;
            while (m != null)
            {
                count++;
                m = m.nextMove;
            }

            return count;
        }
        
        
        /// <summary>
        /// Filters out longest moves
        /// </summary>
        /// <param name="moves">Generated list of moves</param>
        /// <returns></returns>
        protected List<Move> FilterOutLongestMoves(List<Move> moves)
        {
            // calculate lengths of moves
            Dictionary<Move, int> moveLengths = new Dictionary<Move, int>();
            foreach (Move m in moves)
                moveLengths[m] = CountMoveLength(m);

            // get longest move
            int maximumLength = moveLengths.Values.Max();


            // filter out longest moves
            List<Move> filteredMoves = new List<Move>();
            foreach (var pair in moveLengths)
            {
                if (pair.Value == maximumLength)
                    filteredMoves.Add(pair.Key);
            } // foreach

            return filteredMoves;
        }

        /// <summary>
        /// Counts the number of jumped kings
        /// </summary>
        protected int CountNumberOfJumpedKings(Move move)
        {
            // count the number of kings by doing move
            GameBoard.DoMove(move);
            GameBoard.UndoMove(move);

            // count the kings
            Move curMove = move;
            int kingCount = 0;
            while (curMove != null)
            {
                if (curMove.removedFigure.IsKing())
                    kingCount++;
                curMove = curMove.nextMove;
            }

            return kingCount;
        }
       
        /// <summary>
        /// Filters out the maximum number of jumped kings
        /// </summary>
        protected List<Move> FilterOutMaximumKings(List<Move> moves)
        {
            Dictionary<Move, int> maxKings = new Dictionary<Move, int>();
            foreach (Move m in moves)
                maxKings[m] = CountNumberOfJumpedKings(m);

            // get the maximum number of jumped kings
            int maxKingCount = maxKings.Values.Max();

            // filter out maximum number of jumped kings
            List<Move> filteredMoves = new List<Move>();
            foreach (var pair in maxKings)
            {
                if (pair.Value == maxKingCount)
                    filteredMoves.Add(pair.Key);
            } // foreach

            return filteredMoves;
        }

        /// <summary>
        /// Get the index of move step having the nearest jumped king
        /// </summary>
        protected int GetNearestJumpedKing(Move move)
        {
            // get jumped figures
            GameBoard.DoMove(move);
            GameBoard.UndoMove(move);

            // count the kings
            Move curMove = move;
            int kingIndex = 1;
            while (curMove != null)
            {
                if (curMove.removedFigure.IsKing())
                    break;
                curMove = curMove.nextMove;
                kingIndex++;
            }

            return kingIndex;
        }

        /// <summary>
        /// Filters out the nearest jumped kings
        /// </summary>
        protected List<Move> FilterOutNearestJumpedKings(List<Move> moves)
        {
            Dictionary<Move, int> nearestKings = new Dictionary<Move, int>();
            foreach (Move m in moves)
                nearestKings[m] = GetNearestJumpedKing(m);

            // get the nearest king index
            int nearestKingIndex = nearestKings.Values.Min();

            // filter out nearest king jumps
            List<Move> filteredMoves = new List<Move>();
            foreach (var pair in nearestKings)
            {
                if (pair.Value == nearestKingIndex)
                    filteredMoves.Add(pair.Key);
            } // foreach

            return filteredMoves;
        }

        /// <summary>
        /// Returns orientation for figure at given field
        /// </summary>
        public enumManOrientation GetManOrientation(Position figurePos)
        {
            enumFigureType figureType = GameBoard.GetField(figurePos);

            enumManOrientation eOrientation = enumManOrientation.Up;
            if( (figureType.FigureColor() == enumFigureColor.White && WhiteMovesUp == false)
                || (figureType.FigureColor() == enumFigureColor.Black && WhiteMovesUp == true) )
            {
                eOrientation = enumManOrientation.Down;
            }

            return eOrientation;
        }

        /// <summary>
        /// Generates list of jump fields for given figure
        /// </summary>
        protected List<Position> GetJumpFieldForFigure(Position pos, enumDiagonalDir? bIgnoredDirection = null)
        {
            List<Position> list = new List<Position>();
            enumFigureType figureType = GameBoard.GetField(pos);
            if (figureType != enumFigureType.Nothing)
            {
                if (figureType.IsKing() == false)
                {                   
                    list = ManMoveGenerator.GetManJumps(pos, GetManOrientation(pos));
                }
                else
                {
                    list = KingMoveGenerator.GetKingJumps(pos, bIgnoredDirection);
                }
            }

            return list;
        }


        /// <summary>
        /// Generates all possible jumps for given figure
        /// </summary>
        protected List<Move> GenerateJumpsForFigure(Position pos, enumDiagonalDir? eIgnoredDirection = null)
        {
            List<Move> list = new List<Move>();

            List<Position> jumpPositions = GetJumpFieldForFigure(pos, eIgnoredDirection);

            foreach (Position jumpPos in jumpPositions)
            {
                Move sourceMove = ManMoveGenerator.CompleteMoveStep(pos, jumpPos, GetManOrientation(pos));

                // generate list of next moves
                List<Move> nextMoves = new List<Move>();

                // if converted to king , multiple jump stops, for INTERNATIONAL CHECKERS
                // allowed for other types of checkers like Russian Checkers
                if ( ! (sourceMove.convertedToKing == true && ContinueJumpAfterPromotion == false) )
                {
                    GameBoard.DoMove(sourceMove);
                    enumDiagonalDir ignoredDirection = jumpPos.GetDiagonalDir(pos);
                    nextMoves = GenerateJumpsForFigure(sourceMove.endPos, ignoredDirection);
                    GameBoard.UndoMove(sourceMove);
                }

                if (nextMoves.Count > 0)
                {
                    foreach (Move nxtMove in nextMoves)
                    {
                        Move m = sourceMove.Copy();
                        m.nextMove = nxtMove;
                        list.Add(m);
                    }
                }
                else list.Add(sourceMove);
            }

            return list;                   
        }

        /// <summary>
        /// Generates all possible jumps
        /// </summary>
        protected List<Move> GenerateAllPossibleJumps(Position? onlyThisFigure)
        {
            List<Move> list = new List<Move>();

            // if we are investigating multiple jump, possible moves are only for this figure
            if (onlyThisFigure.HasValue)
            {
                list.AddRange(GenerateJumpsForFigure(onlyThisFigure.Value));
                return list;
            }

            for (int row = 0; row < GameBoard.Size; row++)
            {
                for (int col = 0; col < GameBoard.Size; col++)
                {
                    enumFigureType figureType = GameBoard.GetField(row, col);
                    if (figureType != enumFigureType.Nothing)
                    {
                        if (figureType.FigureColor() == GameBoard.OnMove)
                        {
                            list.AddRange(GenerateJumpsForFigure(new Position(row, col)));
                        }
                    }
                }
            }

            return list;
        }

        /// <summary>
        /// Main method. Returns the list of valid jump moves.
        /// </summary>
        public List<Move> GetListOfValidJumps(Position? onlyThisFigure = null)
        {
            // get all possible jumps
            List<Move> jumpList = GenerateAllPossibleJumps(onlyThisFigure);
            if (jumpList.Count == 0)
                return jumpList;

            // if we are investigating multiple jump, there are no capture constraints
            if (onlyThisFigure.HasValue)
            {
                return jumpList;
            }

            // apply the whole priority list of capture constraints for given checkers type
            foreach (var captureConstraint in CaptureConstraintList)
            {
                switch (captureConstraint)
                {
                    case enumCaptureConstraint.None:
                        return jumpList;
                    case enumCaptureConstraint.KingPriority:
                        jumpList = FilterOutKingPriority(jumpList);
                        break;
                    case enumCaptureConstraint.LongestJumpPriority:
                        jumpList = FilterOutLongestMoves(jumpList);
                        break;
                    case enumCaptureConstraint.CaptureMaximumKings:
                        jumpList = FilterOutMaximumKings(jumpList);
                        break;
                    case enumCaptureConstraint.CaptureNearestKing:
                        jumpList = FilterOutNearestJumpedKings(jumpList);
                        break;
                } // switch

                if (jumpList.Count == 0)
                    throw new Exception("Filtered out all jumps. Possible error.");
                if (jumpList.Count == 1)
                    break;
            }

            return jumpList;
        }
    }
}
